package it.crosato.stage.server.model.comparison;

import it.crosato.stage.shared.objects.Match;
import it.crosato.stage.shared.objects.MatchElement;
import it.crosato.stage.shared.objects.Multiset;
import it.crosato.stage.shared.objects.MultisetsGroup;
import java.util.Vector;

public class InvariantsComparator implements IInvariantsComparator {

	@Override
	public double[][] getDistanceMatrix(MultisetsGroup[][] multisetsGroups,
			Vector<Match[][]> matches)  {
		
		int nOrganisms = multisetsGroups.length;
		int nPathways = multisetsGroups[0].length;
				
		double[][] M = new double[nOrganisms][nOrganisms];
		for (int i = 0; i < nPathways; i++) {
			Match[][] matchPath = new Match[nOrganisms][nOrganisms];
			double[][] Mpath = getInvariantsDistanceMatrixForPathway(multisetsGroups, i, matchPath);
			matches.add(matchPath);
			for (int j = 0; j < nOrganisms; j++) {
				for (int k = 0; k < nOrganisms; k++) {
					M[j][k] += Mpath[j][k]/nPathways;
				}
			}
		}	
		return M;
	}

	/**
	 * Calcola la matrice di distanza considerando una singola via metabolica
	 * @param multisetsGrups le vie metaboliche sotto forma di gruppi di multi-insiemi di
	 * reazioni o enzimi che rappresentano i T-invarianti minimali
	 * @param pathway indice della via metabolica da considerare
	 * @param matches insieme dei match tra gli invarianti su cui fare side-effects
	 * @return la matrice delle distanze relativa alla singola via metabolica
	 */
	private double[][] getInvariantsDistanceMatrixForPathway
			(MultisetsGroup[][] multisetsGroups, int pathway, Match[][] matches) {

		int nOrganisms = multisetsGroups.length;
		double[][] Mpath = new double[nOrganisms][nOrganisms];
		for (int i = 0; i < nOrganisms; i++) {
			for (int j = 0; j < nOrganisms; j++) {
				MultisetsGroup multisetGroupX = multisetsGroups[i][pathway];
				MultisetsGroup multisetGroupY = multisetsGroups[j][pathway];
				Match match = new Match();
				Mpath[i][j] = getDifference(multisetGroupX, multisetGroupY, match);
				matches[i][j] = match;
			}
		}
		return Mpath;
	}

	/**
	 * Ritorna la differenza tra due gruppi di multi-insimi, ossia tra due gruppi di
	 * T-invarianti minimali che modellano due vie metaboliche.
	 * @param multisetGroupX il primo multi-insieme
	 * @param multisetGroupY il secondo multi-insieme
	 * @param match un singolo matche su sui fare side-effects
	 * @return
	 */
	private double getDifference(MultisetsGroup multisetGroupX,
			MultisetsGroup multisetGroupY, Match match) {

		int dimX = multisetGroupX.getNumberOfMultisets();
		int dimY = multisetGroupY.getNumberOfMultisets();
		double[][] sorensenIndexes = new double[dimX][dimY];
		for (int i = 0; i < multisetGroupX.getNumberOfMultisets(); i++) {
			Multiset multiX = multisetGroupX.getMultiset(i);
			for (int j = 0; j < multisetGroupY.getNumberOfMultisets(); j++) {
				Multiset multiY = multisetGroupY.getMultiset(j);
				sorensenIndexes[i][j] = Utilities.getSorensenIndex(multiX, multiY);
			}
		}
		
		int maxCard, minCard;
		if (dimX >= dimY) {
			maxCard = dimX;
			minCard = dimY;
		}
		else {
			maxCard = dimY;
			minCard = dimX;
		}
		
		double tot = 0;
		for (int k = 0; k < minCard; k++) {
			double max = -1;
			int invX = -1;
			int invY = -1;
			for (int i = 0; i < dimX; i++) {
				for (int j = 0; j < dimY; j++) {
					if (sorensenIndexes[i][j] > max) {
						max = sorensenIndexes[i][j];
						invX = i;
						invY = j;
					}
				}
			}
			match.addMatchElement(new MatchElement(invX, invY, 1 - max));
			tot += max;
			for (int i = 0; i < dimX; i++) {
				sorensenIndexes[i][invY] = -1;
			}
			for (int i = 0; i < dimY; i++) {
				sorensenIndexes[invX][i] = -1;
			}
		}
		return 1-(tot/maxCard);
	}
}